package issr.market.db.impl;

import issr.market.db.DBController;
import issr.market.db.hibernate.HibernateFactory;
import issr.market.init.Configuration;
import issr.market.model.ErogazioneProdotto;
import issr.market.model.Lotto;
import issr.market.model.Prodotto;
import issr.market.model.Ricevuta;
import issr.market.model.Utente;
import issr.market.utils.Constants;
import issr.market.utils.Utils;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;

public class DBControllerImpl implements DBController {
	
	private Logger logger = Logger.getLogger(DBController.class);
	
	public Session getSession(){
		return HibernateFactory.getInstance().getCurrentSession();
	}
	
	public boolean isEmptyUserDB(){
		
		boolean esito = false;
		
		try{
			
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Utente.class);
			
			logger.info("Utenti presenti nel database: "+criteria.list().size());
			esito = criteria.list().size() == 0;
		}
		catch(Exception e){
			
			logger.error("Errore: "+e.getMessage());
			esito = false;
			if (getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
		
	}
	
	public void creaNuovoUtente(String nome, String login, String password,
			 String email, String telefono, String ruolo,
			 Long dataRegistrazione, Long scadenzaRegistrazione, Long scadenzaPassword) {
		
		try {
			getSession().beginTransaction();
			
			Utente utente = new Utente();
			utente.setNomeUtente(nome);
			utente.setLogin(login);
			utente.setPassword(Utils.encrypt(password));
			utente.setEmail(email);
			utente.setTelefono(telefono);
			utente.setRuolo(ruolo);
			utente.setDataRegistrazione(dataRegistrazione);
			utente.setScadenzaRegistrazione(scadenzaRegistrazione);
			utente.setScadenzaPassword(scadenzaPassword);
			
			getSession().save(utente);
			getSession().getTransaction().commit();
			
		} catch (Exception e) {
			logger.error("Errore nella creazione dell'utente. "+e.getMessage());
			if (getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
	}
	
	public void modificaUtente(int id, String nome, String login, String password,	
			String email, String telefono, String ruolo,
			Long scadenzaRegistrazione, Long scadenzaPassword){
		
		String query = "Update utenti "+
						"set nomeUtente = '"+nome+"'"+
						",login = '"+login+"'"+
						",email = '"+email+"'"+
						",telefono = '"+telefono+"'"+
						",ruolo = '"+Utils.numRuoloToString(Integer.valueOf(ruolo))+"'"+
						((password==null || "".equals(password))? "" : ",password = '"+Utils.encrypt(password)+"'")+
						((scadenzaPassword==null)? "": ",scadenzaPassword = "+scadenzaPassword)+
						((scadenzaRegistrazione==null)? "" : ",scadenzaRegistrazione = "+scadenzaRegistrazione)+
						" where idUtente = "+id;
		
		try{
			
			logger.info("Query update: "+query);
			getSession().beginTransaction();
			
			getSession().createSQLQuery(query).executeUpdate();
			
			getSession().getTransaction().commit();
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
	}
	
	
	public void eliminaUtenti(List<String> idUtenti){
		
		String query = "Delete from utenti where idUtente in ("+Utils.commatize(idUtenti)+")";
		
		try{
			getSession().beginTransaction();
			
			getSession().createSQLQuery(query).executeUpdate();
			getSession().getTransaction().commit();
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
	}
	
	
	public Utente loginUtente(String login, String password){
		
		Utente utente = null;
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Utente.class);
			criteria.add(Restrictions.eq("login", login));
			criteria.add(Restrictions.eq("password", Utils.encrypt(password)));
			
			utente = (Utente)criteria.list().get(0);
		}
		catch(Exception e){
			System.out.println("Eccezione "+e.getMessage());
			utente = null;
			if (getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return utente;
	}
	
	@SuppressWarnings("unchecked")
	public List<Utente> getUtenti(String sortingField){
		
		List<Utente> utenti = null;
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Utente.class);
			if(sortingField!=null && !"".equals(sortingField))
				criteria.addOrder(Order.asc(sortingField));
			
			utenti = criteria.list();
		}
		catch(Exception e){
			logger.error(e, e);
			utenti = null;
			if (getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return utenti;
	}
	
	public boolean inserisciNuovaTipologiaProdotti(List<Prodotto> prodotti){
		boolean esito = false;
		
		try{
			getSession().beginTransaction();
			for(Prodotto prodotto : prodotti){
				Criteria criteria = getSession().createCriteria(Prodotto.class);
				criteria.add(Restrictions.eq("idProdotto", prodotto.getIdProdotto()));
				if(criteria.uniqueResult()==null){
					prodotto.setStatoDisponibile(Constants.PRODOTTO_ORDINATO);
					getSession().save(prodotto);
				}
			}
			
			esito = true;
			
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
			
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
		
	}

	public Lotto depositoLotto(String idLotto, String idProdotto, Long dataConsegna, Long dataScadenza, int quantitaProdotti, int stato){
		
		Lotto lotto = new Lotto();
		try{
			lotto.setIdLotto(idLotto);
			lotto.setDataConsegna(dataConsegna);
			lotto.setDataScadenza(dataScadenza);
			lotto.setQuantitaProdotti(quantitaProdotti);
			lotto.setStato(stato);
			
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Prodotto.class);
			criteria.add(Restrictions.eq("idProdotto", idProdotto));
			
			Prodotto prodotto = (Prodotto)criteria.uniqueResult();
			
			lotto.setProdotto(prodotto);
			
			getSession().save(lotto);
			getSession().getTransaction().commit();
			
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
			lotto = null;
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return lotto;
	}
	
	
	@SuppressWarnings("unchecked")
	public List<Lotto> approvaLotti(){
		
		List<Lotto> lotti = null;
		
//		String query = "UPDATE prodotti SET stato = "+Constants.PRODOTTO_APPROVATO+" WHERE stato = "+Constants.PRODOTTO_INSERITO;
		
		try{
			getSession().beginTransaction();
//			getSession().createSQLQuery(query).executeUpdate();
			Criteria criteria = getSession().createCriteria(Lotto.class).add(Restrictions.eq("stato", Constants.LOTTO_INSERITO));
			lotti = criteria.list();
			if(lotti != null)
				for(Lotto lotto : lotti){
					lotto.setStato(Constants.LOTTO_APPROVATO);
					lotto.getProdotto().setStatoDisponibile(Constants.PRODOTTO_DISPONIBILE);
				}
			
			getSession().getTransaction().commit();
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
			lotti = null;
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		return lotti;
		
	}
	
	public void eliminaLotti(String[] idLotti){
		
		String query = "Delete from lotti where idLotto "+(idLotti.length>1?"in ("+Utils.commatize(Arrays.asList(idLotti), true)+")":"= '"+idLotti[0]+"'");
		
		logger.info(query);
		try{
			getSession().beginTransaction();
			
			getSession().createSQLQuery(query).executeUpdate();
			getSession().getTransaction().commit();
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
	
	}
	
	@SuppressWarnings("unchecked")
	public List<Lotto> getLottiByStato(int stato){
		List<Lotto> lotti = null;
		
		try{
			getSession().beginTransaction();
			Criteria criteria = getSession().createCriteria(Lotto.class);
			criteria.add(Restrictions.eq("stato", stato));
			
			lotti = criteria.list();
			
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return lotti;
	}
	
	@SuppressWarnings("unchecked")
	public List<Prodotto> getProdotti(){
		List<Prodotto> prodotti = null;
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Prodotto.class);
			prodotti = criteria.list();
			
			getSession().getTransaction().commit();
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return prodotti;
	}
	
	@SuppressWarnings("unchecked")
	public List<Prodotto> getProdottiDaOrdinare(){
		
		List<Prodotto> prodotti = null;
		
		String query = "SELECT * FROM prodotti p1 " +
						"WHERE p1.st_disponibile = "+ Constants.PRODOTTO_ESAURITO+
						" UNION " +
						"SELECT * FROM prodotti p2 " +
						"WHERE (" +
							"SELECT COUNT(*) FROM lotti l WHERE l.idProdotto = p2.idProdotto" +
						") = 1" +
						" AND " +
						"p2.st_disponibile != 2";
		
		try{
			getSession().beginTransaction();
			prodotti = getSession().createSQLQuery(query).addEntity(Prodotto.class).list();
			getSession().getTransaction().commit();
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return prodotti;
	}
	
	@SuppressWarnings("unchecked")
	public List<Lotto> getProdottiRestituibili(String[] idLotti){
		List<Lotto> prodotti = null;
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Lotto.class);
			criteria.add(Restrictions.in("idLotto", idLotti));
			criteria.add(Restrictions.ne("stato", Constants.LOTTO_SCADUTO));
			
			prodotti = criteria.list();
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
			prodotti = null;
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return prodotti;
	}
	
	@SuppressWarnings("unchecked")
	public Map<String, List<Lotto>> getProdottiErogazione(String[] idProdotti){
		
		Map<String, List<Lotto>> lottiPerProdottoMap = new HashMap<String, List<Lotto>>();
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Prodotto.class)
								.add(Restrictions.in("idProdotto", idProdotti));
			
			List<Prodotto> prodotti = criteria.list();
			
			for(Prodotto prodotto : prodotti){
				Criteria criteriaLotto = getSession().createCriteria(Lotto.class)
									.add(Restrictions.eq("prodotto", prodotto))
									.addOrder(Order.asc("dataScadenza"));
				List<Lotto> lottiPerProdotto = criteriaLotto.list();
				lottiPerProdottoMap.put(prodotto.getIdProdotto(), lottiPerProdotto);
			}
			
			getSession().getTransaction().commit();
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return lottiPerProdottoMap;
	}
	
	
	public void insertRicevuta(String idRicevuta, Long dataEmissione, double prezzoTotale){
		
		try{
			getSession().beginTransaction();
			
			Ricevuta ricevuta = new Ricevuta();
			ricevuta.setIdRicevuta(idRicevuta);
			ricevuta.setDataEmissione(dataEmissione);
			ricevuta.setPrezzoTotale(prezzoTotale);
			
			getSession().save(ricevuta);
			getSession().getTransaction().commit();
		}
		catch(Exception e){
			logger.error(e,e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
	}
	
	public int updateQuantitaProdotti(ErogazioneProdotto eProd, boolean subtract){
		
		int newQuantita = -1;
		
		Lotto lotto = null;
		
		try{
			getSession().beginTransaction();
			Criteria criteria = getSession().createCriteria(Lotto.class);
			criteria.add(Restrictions.eq("idLotto", eProd.getIdLotto()));
			lotto = (Lotto)criteria.uniqueResult();
			
			if(lotto == null) return -1;
			
			if(subtract)
				newQuantita = lotto.getQuantitaProdotti()-eProd.getQuantita();
			else newQuantita = lotto.getQuantitaProdotti()+eProd.getQuantita();
			lotto.setQuantitaProdotti(newQuantita);
			
			getSession().save(lotto);
			getSession().getTransaction().commit();
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return newQuantita;
	}
		
	@SuppressWarnings("unchecked")
	public List<Lotto> updateStatoLotti(List<String> idLotti, int stato){
		List<Lotto> lotti = null;
//		String updateQuery = "UPDATE lotti SET stato = "+stato+
//				" WHERE idLotto in ("+Utils.commatize(idLotti, true)+")";

//		logger.info(updateQuery);
		try{
			getSession().beginTransaction();
//			getSession().createSQLQuery(updateQuery).executeUpdate();
			Criteria criteria = getSession().createCriteria(Lotto.class);
			criteria.add(Restrictions.in("idLotto", idLotti));
			
			lotti = (List<Lotto>)criteria.list();
			for(Lotto lotto : lotti){
				lotto.setStato(stato);
				getSession().save(lotto);
			}	
			
			getSession().getTransaction().commit();
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return lotti;
	}
	
	public boolean aggiornaStatoProdotti(List<Prodotto> prodotti, int stato){
		boolean esito = false;
		
//		String query = "UPDATE prodotti SET st_disponibile = "+stato+" WHERE idProdotto in ("+Utils.commatize(Arrays.asList(idProdotti),true)+")";
		
		try{
			getSession().beginTransaction();
//			getSession().createSQLQuery(query).executeUpdate();
			for(Prodotto prodotto : prodotti){
				Criteria criteria = getSession().createCriteria(Prodotto.class);
				criteria.add(Restrictions.eq("idProdotto", prodotto.getIdProdotto()));
				
				Prodotto prodottoPresente = (Prodotto)criteria.uniqueResult();
				if(prodottoPresente!=null){
					logger.info("Aggiorno a stato "+stato+" il prodotto presente in DB con nome "+prodottoPresente.getNomeProdotto()+", ID: "+prodottoPresente.getIdProdotto());
					prodottoPresente.setStatoDisponibile(stato);
					getSession().save(prodottoPresente);
				}
				else{
					
					logger.info("Inserisco nuova tipologia di prodotto in DB per "+prodotto.getNomeProdotto()+", con ID: "+prodotto.getIdProdotto());
					prodotto.setErogati(0);
					prodotto.setImmagazzinati(0);
					prodotto.setRestituiti(0);
					prodotto.setRichiesti(0);
					prodotto.setScaduti(0);
					prodotto.setStatoDisponibile(stato);
					getSession().save(prodotto);
				}
			
			}
			
			getSession().getTransaction().commit();
			
			esito = true;
		}catch(Exception e){
			logger.error(e,e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
	}
	
	public boolean aggiornaStatoProdotto(String idProdotto, int stato){
		boolean esito = false;
		
		String query = "UPDATE prodotti SET st_disponibile = "+stato+" WHERE idProdotto = '"+idProdotto+"'";
		
		try{
			getSession().beginTransaction();
			getSession().createSQLQuery(query).executeUpdate();
			getSession().getTransaction().commit();
			esito = true;
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
	}

	
	public boolean checkRicevuta(String idRicevuta){
		boolean esito = false;
		
		Calendar data = Calendar.getInstance();
		data.set(Calendar.YEAR, -Configuration.validitaRicevuta);
		long checkData = data.getTime().getTime();
		
		try{
			getSession().beginTransaction();
			Criteria criteria = getSession().createCriteria(Ricevuta.class);
			criteria.add(Restrictions.eq("idRicevuta", idRicevuta));
			criteria.add(Restrictions.ge("dataEmissione", checkData));
			
			esito = criteria.list().size()>0;
		}
		catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
			esito = false;
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
	}
	
	
	public boolean aggiornaTabellaIndici(String idProdotto, Map<String, Integer> indiciMap){
		boolean esito = false;
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Prodotto.class);
			criteria.add(Restrictions.eq("idProdotto", idProdotto));
			
			Prodotto prodotto = (Prodotto)criteria.uniqueResult();
			
			//Recupero valori indici da indiciMap 
			Iterator<String> keyItr = indiciMap.keySet().iterator();
			while(keyItr.hasNext()){
				String key = keyItr.next();
				Integer value = indiciMap.get(key);
				//aggiorno i valori tramite reflection
				Integer oldValue = (Integer)prodotto.getClass().getMethod(Utils.generateGetterForPrefix(key)).invoke(prodotto);
				prodotto.getClass().getMethod(Utils.generateSetterForPrefix(key), value.getClass()).invoke(prodotto, oldValue+value);
			}
			
			getSession().save(prodotto);
			getSession().getTransaction().commit();
			
			esito = true;
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
		
	}
	
	public boolean aggiornaIndice(String idProdotto, String indexKey, Integer value){
		boolean esito = false;
		
		try{
			getSession().beginTransaction();
			
			Criteria criteria = getSession().createCriteria(Prodotto.class);
			criteria.add(Restrictions.eq("idProdotto", idProdotto));
			
			Prodotto prodotto = (Prodotto)criteria.uniqueResult();
			
			//aggiorno i valori tramite reflection
			Integer oldValue = (Integer)prodotto.getClass().getMethod(Utils.generateGetterForPrefix(indexKey)).invoke(prodotto);
			logger.info("OLD VALUE: "+oldValue);
			prodotto.getClass().getMethod(Utils.generateSetterForPrefix(indexKey), value.getClass()).invoke(prodotto, oldValue+value);
			logger.info("NEW VALUE: "+(oldValue+value));
			
			if(indexKey.equals(Constants.IMMAGAZZINATI_KEY))
				prodotto.setStatoDisponibile(Constants.PRODOTTO_DISPONIBILE);
			
			getSession().save(prodotto);
			getSession().getTransaction().commit();
			
			esito = true;
			
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return esito;
		
	}
	
	public int getTotaleImmagazzinati(){
		int totale = 0;
		
		try{
			getSession().beginTransaction();
			BigDecimal big = (BigDecimal)getSession().createSQLQuery("SELECT SUM(immagazzinati) from prodotti").uniqueResult();
			totale = big.intValue();
		}catch(Exception e){
			logger.error(e, e);
			if(getSession().getTransaction().isActive())
				getSession().getTransaction().rollback();
		}
		finally{
			if(getSession()!=null && getSession().isOpen())
				getSession().close();
		}
		
		return totale;
	}
	

}
